nvim | 您所在的位置:网站首页 › vim脚本 call命令 › nvim |
在 neovim 中使用 Lua
nvim-lua-guide 中文版简易教程 译者:Neovim Core Developer
Lua 作为 Neovim 中的一等语言的集成正在成为它的杀手级特性之一。然而,学习如何用 Lua 编写插件的教程数量并不像用 Vimscript 编写插件那样多。这是一种尝试,试图提供一些基本信息,让人们可以使用 Lua 编写 Neovim 插件。 本指南假定您使用的是 Neovim 0.5+ 学习 Lua不同于原版教程,以下资源适用于国内用户: 在 Y 分钟内学习 X 关于 Lua 的页面 Lua 菜鸟教程 Lua 用户维基 Lua 的官方参考手册Lua 是一种非常干净和简单的语言。它很容易学习,特别是如果你有其他编程语言基础的例如 TypeScript / JavaScript 等,会更加容易上手 Lua。注意:Neovim 嵌入的 Lua 版本是 LuaJIT 2.1.0,它与 Lua 5.1 保持兼容(带有几个 5.2 扩展) 现有的一些在 Neovim 中使用 Lua 的教程已经编写了一些教程来帮助人们用 Lua 编写插件。他们中的一些人在写这本指南时提供了不少的帮助。非常感谢它们的作者。 teukka.tech - 从 init.vim 转到 init.lua 2n.pl - 如何使用 Lua 编写 neovim 插件 2n.pl - 如何使用 Lua 制作 neovim UI ms-jpq - NeoVim 异步教程 相关插件 Vimpeccable - Plugin to help write your .vimrc in Lua plenary.nvim - All the lua functions I don't want to write twice popup.nvim - An implementation of the Popup API from vim in Neovim nvim_utils nvim-luadev - REPL/debug console for nvim lua plugins nvim-luapad - Interactive real time neovim scratchpad for embedded lua engine nlua.nvim - Lua Development for Neovim galaxyline.nvim - neovim statusline plugin written in lua BetterLua.vim - Better Lua syntax highlighting in Vim/NeoVim Lua 文件位置 init.luaNeovim 支持从 init.lua 文件加载配置而不是通常的 init.vim 文件。 注意:init.lua 文件是完全可选的。Neovim 仍然支持从 init.vim 加载配置。请记住,Neovim 的一些功能还没有 100% 暴露给 Lua 模块部分。 模块Lua 模块通常位于您的 runtimepath 中的 lua/ 文件夹中(对于大多数用户来说,在 *nix 系统上为 ~/.config/nvim/lua,在 Windows 系统上为 ~/appdata/Local/nvim/lua)。这意味着您可以 require() 这些文件作为 Lua 模块 我们以下面的文件夹结构为例: 📂 ~/.config/nvim ├── 📁 after ├── 📁 ftplugin ├── 📂 lua │ ├── 🌑 myluamodule.lua │ └── 📂 other_modules │ ├── 🌑 anothermodule.lua │ └── 🌑 init.lua ├── 📁 pack ├── 📁 plugin ├── 📁 syntax └── 🇻 init.vim下面的 Lua 代码将加载 myluamodule.lua require('myluamodule')注意没有 .lua 扩展名。 类似地,加载 other_modules/anothermodule.lua 的过程如下: require('other_modules.anothermodule') -- or require('other_modules/anothermodule')路径分隔符可以用点 . 表示,也可以用斜杠 / 表示。 文件夹如果包含 init.lua 文件,可以直接引用该文件夹而不必指定该文件的名称 require('other_modules') -- loads other_modules/init.lua加载一个不存在的模块或者加载的模块有语法错误会直接中止当前正在执行的脚本。pcall() 函数可以用来处理这类错误 local ok, _ = pcall(require, 'module_with_error') if not ok then -- not loaded end更多信息请参见: :help lua-require 提示多个 Lua 插件在它们的 lua/ 文件夹中可能有相同的文件名。这可能会导致命名空间冲突。如果两个不同的插件有一个 lua/main.lua 文件,那么执行 require('main') 是不明确的:我们想要加载哪个文件?最好将您的配置或插件命名为顶级文件夹, 例如这样的形式:lua/plugin_name/main.lua。 运行时文件和 Vimscript 文件很像,位于 runtimepath 中的一些特殊目录中的 Lua 文件可以被 Neovim 自动加载。目前有以下这些特殊目录: colors/ compiler/ ftplugin/ indent/ plugin/ syntax/注意:在同一个运行时目录中,*.vim 文件会先于所有的 *.lua 文件被加载。 更多信息请参见: :help 'runtimepath' :help load-plugins 提示因为运行时文件并不基于 Lua 模块系统,所以两个不同的插件都拥有 plugins/main.lua 文件是没有任何问题的。 在 Vimscript 中使用 Lua :lua该命令执行一段 Lua 代码 :lua require('myluamodule')可以使用以下语法编写多行脚本: echo "Here's a bigger chunk of Lua code" lua ', ']', {remap = true}) -- :nmap > ] -- 除非是递归映射,否则 映射不会起作用,vim.keymap.set() 会自动为你处理 vim.keymap.set('n', 'plug', '(plugin)') -- :nmap plug (plugin)在 expr 映射中,nvim_replace_termcodes() 会被自动应用于 Lua 函数返回的字符串 vim.keymap.set('i', '', function() return vim.fn.pumvisible == 1 and '' or '' end, {expr = true})更多信息请参见: :help recursive_mappingvim.keymap.del() 工作原理相同,但是效果是删除映射: vim.keymap.del('n', 'ex1') vim.keymap.del({'n', 'c'}, 'ex2', {buffer = true}) 定义用户命令
Neovim 提供了如下 API 函数来创建用户命令 全局用户命令 vim.api.nvim_create_user_command() vim.api.nvim_del_user_command() Buffer-local 的用户命令 vim.api.nvim_buf_create_user_command() vim.api.nvim_buf_del_user_command()以 vim.api.nvim_create_user_command() 为例说明用法 此函数的第一个参数是命令的名字(必须以大写字母开头)。 第二个参数是调用该命令时要执行的代码。它可以是: 一个字符串(在这种情况下它将作为 Vimscript 执行)。你可以像使用 :command 一样使用转义序列,譬如 、 等 vim.api.nvim_create_user_command('Upper', 'echo toupper()', { nargs = 1 }) -- :command! -nargs=1 Upper echo toupper() vim.cmd('Upper hello world') -- prints "HELLO WORLD"或者是一个 Lua 函数。它接受一个类似字典的 table,其中包含通常是由转义序列提供的数据(可用的所有键请参见 :help nvim_create_user_command()) vim.api.nvim_create_user_command( 'Upper', function(opts) print(string.upper(opts.args)) end, { nargs = 1 } )第三个参数是一个包含命令属性的 table(请参见 :help command-attributes)。值得注意的是,由于你可以使用 vim.api.nvim_buf_create_user_command() 来创建 buffer-local 的用户命令,所以 -buffer 不是一个有效的属性。 此外还有两种属性可用: desc 属性是你在定义为 Lua 回调函数的命令上运行 :command {cmd} 时显示的内容。与上文中的键盘映射类似,我们建议在定义为 Lua 函数的命令中加入此属性。 force 属性相当于调用 :command! 并且替换已经存在的同名命令。与 Vimscript 不同,它的默认值为 true除了 :help :command-complete 中列出的属性之外,-complete 还可以设置为一个 Lua 函数。 vim.api.nvim_create_user_command('Upper', function() end, { nargs = 1, complete = function(ArgLead, CmdLine, CursorPos) -- return completion candidates as a list-like table return { 'foo', 'bar', 'baz' } end, })Buffer-local 的用户命令把 buffer 编号作为第一个参数。这比 -buffer 更优,后者只能为当前的 buffer 定义命令。 vim.api.nvim_buf_create_user_command(4, 'Upper', function() end, {})vim.api.nvim_del_user_command() 的参数是要删除命令的名字。 vim.api.nvim_del_user_command('Upper') -- :delcommand Upper同样的,vim.api.nvim_buf_del_user_command() 也把 buffer 编号作为第一个参数,0 代表当前的 buffer。 vim.api.nvim_buf_del_user_command(4, 'Upper')更多信息请参见: :help nvim_create_user_command :help 40.2 :help command-attributes Caveats-complete=custom 属性自动选出合适的候选补全并且支持内置的通配符(:help wildcard) function! s:completion_function(ArgLead, CmdLine, CursorPos) abort return join([ \ 'strawberry', \ 'star', \ 'stellar', \ ], "\n") endfunction command! -nargs=1 -complete=custom,s:completion_function Test echo " Typing `:Test st[ae]` returns "star" and "stellar"把 complete 设置为 Lua 函数的话,它的行为会类似于 customlist ,Neovim 不会筛选函数返回的候选列表 vim.api.nvim_create_user_command('Test', function() end, { nargs = 1, complete = function(ArgLead, CmdLine, CursorPos) return { 'strawberry', 'star', 'stellar', } end, }) -- Typing `:Test z` returns all the completion results because the list was not filtered 定义自动命令(本节内容尚未完成) Neovim 0.7.0 为创建自动命令提供了相应的 API 函数。更多细节请参见 :help api-autocmd Pull request #14661 (lua: autocmds take 2) 定义语法高亮(本节内容尚未完成) Neovim 0.7.0 为处理高亮组提供了相应的 API 函数。更多信息请参见: :help nvim_set_hl() :help nvim_get_hl_by_id() :help nvim_get_hl_by_name() General tips and recommendations 重新加载缓存的模块在 Lua 中,require() 函数会缓存已加载的模块,这对提升性能是非常有用的。但是它会对插件的工作造成影响,因为已加载的模块不会在后续的 require() 调用中更新。 如果你想刷新某个特定模块的缓存,那么你必须去修改全局的 package.loaded : package.loaded['modname'] = nil require('modname') -- loads an updated version of module 'modname'nvim-lua/plenary.nvim 插件提供了实现此功能的自定义函数 不要填充 Lua 字符串!当使用双重中括号的字符串时,尽量不要填充多余的字符(如空格、制表符等)!当字符没有特殊意义的时候这样做无可非议;但是当字符具有特殊意义时,这样做可能会导致一些难以发现的问题 vim.api.nvim_set_keymap('n', 'f', [[ call foo() ]], {noremap = true})在上面的例子中,f 被映射到了 call foo() ,而不是我们期望的 call foo()。 关于 Vimscript Lua 之间类型转换的注意事项 变量转换时会创建一个副本这意味着你对对象(从 Lua 转换到 Vimscript 的对象或者反过来)的引用进行修改不会影响到原对象。 例如,Vimscript 中的 map() 函数就地修改了一个变量: let s:list = [1, 2, 3] let s:newlist = map(s:list, {_, v -> v * 2}) echo s:list " [2, 4, 6] echo s:newlist " [2, 4, 6] echo s:list is# s:newlist " 1在 Lua 中调用这个函数,它改变的将会是参数的一个副本: local tbl = {1, 2, 3} local newtbl = vim.fn.map(tbl, function(_, v) return v * 2 end) print(vim.inspect(tbl)) -- { 1, 2, 3 } print(vim.inspect(newtbl)) -- { 2, 4, 6 } print(tbl == newtbl) -- false 并不是总能进行类型转换这主要影响函数和 table: 混合列表和字典的 Lua table 是无法转换的: print(vim.fn.count({1, 1, number = 1}, 1)) -- E5100: Cannot convert given lua table: table should either have a sequence of positive integer keys or contain only string keys尽管你可以在 Lua 中通过 vim.fn 调用 VIm 函数,但是你不能保存对它们的引用。这可能会导致一些意外的行为: local FugitiveHead = vim.fn.funcref('FugitiveHead') print(FugitiveHead) -- vim.NIL vim.cmd("let g:test_dict = {'test_lambda': {-> 1}}") print(vim.g.test_dict.test_lambda) -- nil print(vim.inspect(vim.g.test_dict)) -- {}把 Lua 函数作为参数传给 Vim 函数是可行的,但是不能把它们存在 Vim 变量中(Neovim 0.7.0+ 中修复了这个问题): -- This works: vim.fn.jobstart({'ls'}, { on_stdout = function(chan_id, data, name) print(vim.inspect(data)) end }) -- This doesn't: vim.g.test_dict = {test_lambda = function() return 1 end} -- Error: Cannot convert given lua type值得注意的是,在 Vimscript 中使用 luaeval() 执行相同操作却是可行的: let g:test_dict = {'test_lambda': luaeval('function() return 1 end')} echo g:test_dict " {'test_lambda': function('4714')} Vim booleans在 Vim 脚本中,一种常见的情况是使用 1 或 0 来代表布尔值。事实上,直到版本 7.4.1154,Vim 才有单独的布尔类型。 在 Vimscript 中,Lua 的布尔值会被转换为真正的布尔值,而不是数字: lua vim.g.lua_true = true echo g:lua_true " v:true lua vim.g.lua_false = false echo g:lua_false " v:false 设置 linters/language servers如果你使用 liner 和 / 或 language server 来获得 Lua 项目的自动补全和错误检查,你可能需要为它们配置 Neovim 特定的设置。以下是一些流行工具的推荐配置: luacheck你可以通过把此配置放入 ~/.luacheckrc (或 $XDG_CONFIG_HOME/luacheck/.luacheckrc)中来让 luacheck 识别全局的 vim 变量: globals = { "vim", }Alloyed/lua-lsp 使用 luacheck 提供 linting 并读取相同的文件。 有关如何配置 luacheck 的更多信息,请参见它的文档 sumneko/lua-language-servernvim-lspconfig 仓库中包含了如何配置 sumneko/lua-language-server 的说明(示例使用内置 LSP 客户端,但其他 LSP 客户端实现的配置应该相同)。 有关如何配置 sumneko/lua-language-server 的更多信息,请参阅 "Setting" coc.nvimcoc.nvim 的 rafcamlet/coc-nvim-lua 补全源为 Neovim 标准库提供了补全项。 调试 Lua 代码你可以使用 jbyuki/one-small-step-for-vimkind 调试在单独的 Neovim 实例中运行的 Lua 代码 该插件使用 Debug Adapter Protocol。连接到一个 debug adapter 需要 DAP 客户端,例如 mfussenegger/nvim-dap 或 puremourning/vimspector。 调试 Lua 设置的按键映射 / 用户命令 / 自动命令:verbose 命令允许你查看 按键映射 / 用户命令 / 自动命令的定义位置: :verbose map m n m_ * echo 'example' Last set from ~/.config/nvim/init.vim line 26默认情况下,出于性能原因,此功能在 Lua 中是禁用的。你可以通过使用大于 0 的 verbose level 启动 Neovim 来启用它: nvim -V1更多信息请参见: :help 'verbose' :help -V neovim/neovim#15079 测试 Lua 代码 plenary.nvim: test harness notomo/vusted 使用 Luarocks 包wbthomason/packer.nvim 支持 Luarocks 包。它的 README 中提供了有关如何设置的说明 Miscellaneous vim.loopvim.loop 是暴露 LibUV 接口的模块。一些相关资源: Official documentation for LibUV Luv documentation teukka.tech - Using LibUV in Neovim更多信息请参见: :help vim.loop vim.lspvim.lsp 是内置的 lsp 库。官方的 lsp 配置插件 neovim/nvim-lspconfig 包含一些流行的 language server 的默认配置。 客户端的行为可以使用 "sp-handlers" 进行配置。更多信息请参见: :help lsp-handler neovim/neovim#12655 how to migrate from diagnostic-nvim你可能还想了解一下一些基于 LSP 客户端构建的插件 更多信息请参见: :help lsp vim.treesittervim.treesitter 是 Tree-sitter 的 neovim 集成,如果你想了解更多可以参考 presentation (38:37). The nvim-treesitter organisation hosts various plugins taking advantage of the library. glepnir 制作的主题 zephyr-nvim 语法高亮基于 nvim-treesitter —— 译者注 See also: :help lua-treesitter Transpilers使用 Lua 的一个优点是您实际上不必编写 Lua 代码!有许多其他语言可以转译到 Lua。 Moonscript可能是 Lua 最著名的转译器之一。添加了许多方便的功能,如类、列表推导或函数字面量。 svermeulen/nvim-moonmaker 插件允许您直接在 Moonscript 中编写 Neovim 插件和配置。 Fennel可以编译为 Lua 的 lisp 方言。你可以使用 Olical/aniseed 或 Hotpot 插件在 Fennel 中为 Neovim 编写配置和插件。此外,Olical/conjure 插件提供了一个支持 Fennel(以及其他语言)的交互式开发环境。 TealTeal 这个名字来自 TL(typed lua)的发音。这代表了它的目标——向 lua 添加强类型,同时语法尽量保持接近标准 lua 语法。 nvim-teal-maker 插件可用于直接在 Teal 中编写 Neovim 插件或配置文件 其他一些有趣的项目: TypeScriptToLua/TypeScriptToLua teal-language/tl Haxe SwadicalRag/wasm2lua hengestone/lua-languages |
CopyRight 2018-2019 实验室设备网 版权所有 |